package models

import (
	"context"
	"encoding/json"
	"fmt"
	"ginchat/utils"
	"net"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/gorilla/websocket"
	"github.com/spf13/viper"
	"gopkg.in/fatih/set.v0"
	"gorm.io/gorm"
)

type Message struct {
	gorm.Model
	UserId     int64  //发送者
	TargetId   int64  // 接收者
	Type       int    // 消息类型 群聊 私聊 广播
	Media      int    // 消息类型 文字 图片 音频
	Content    string // 消息内容
	Pic        string // 图片
	Url        string // 链接
	Desc       string // 描述
	Amount     int    // 其他数字统计
	CreateTime uint64 //创建时间
	ReadTime   uint64 //读取时间
}

func (table *Message) TableName() string {
	return "message"
}

type Node struct {
	Conn          *websocket.Conn
	Addr          string        //客户端地址
	DataQueue     chan []byte   // 通道
	HeartbeatTime uint64        //心跳时间
	LoginTime     uint64        //登录时间
	GroupSets     set.Interface // go get gopkg.in/fatih/set.v0
}

// 映射关系
var clientMap map[int64]*Node = make(map[int64]*Node)

// 读写锁
var rwLocaker sync.RWMutex

// 聊天 需要 发送者ID 消息类型 发送的内容  发送类型
func Chat(writer http.ResponseWriter, request *http.Request) {
	// 校验token
	// token := query.get("token")
	query := request.URL.Query()
	id := query.Get("userId")
	userId, _ := strconv.ParseInt(id, 10, 64)
	// msgType, _ := strconv.Atoi(query.Get("type"))
	// targetId := query.Get("targetId")
	// context := query.Get("context")
	isvalida := true
	conn, err := (&websocket.Upgrader{
		// 校验token
		CheckOrigin: func(r *http.Request) bool {
			return isvalida
		},
	}).Upgrade(writer, request, nil)
	if err != nil {
		fmt.Println(err)
		return
	}

	//2. 获取conn
	currentTime := uint64(time.Now().Unix())
	node := &Node{
		Conn: conn,
		/*
		   make(chan []byte, 50) 表示创建了一个带有缓冲区的通道，通道中的元素类型为 []byte，并且设置了缓冲区的大小为 50。

		   具体来说，这个语法包含两部分：

		   make(chan []byte): 创建一个通道，元素类型为 []byte，不带缓冲区。这种通道称为无缓冲通道，发送到无缓冲通道的数据会被直接传递给接收者，发送者会等待接收者接收数据后才能继续执行。

		   , 50: 在第一部分的基础上，添加了缓冲区的大小，为 50。这意味着通道的容量为 50，可以在发送数据到通道时不立即被接收者接收，而是将数据放入缓冲区，直到缓冲区满了才会阻塞发送者。因此，对于有缓冲的通道，发送者可以继续发送数据，直到缓冲区满了为止，而不会阻塞发送操作。

		   总之，make(chan []byte, 50) 创建了一个带有缓冲区的通道，缓冲区大小为 50，可以缓存最多 50 个 []byte 类型的元素。
		*/
		Addr:          conn.RemoteAddr().String(), //客户端地址
		HeartbeatTime: currentTime,                //心跳时间
		LoginTime:     currentTime,                //登录时间
		DataQueue:     make(chan []byte, 50),
		GroupSets:     set.New(set.ThreadSafe),
	}
	// 3. 用户关系
	// 4. userid跟node绑定 并枷锁
	rwLocaker.Lock()
	clientMap[userId] = node
	rwLocaker.Unlock()
	// 5.完成数据发送逻辑
	go sendProc(node)
	// 6. 完成接收逻辑
	go recvProc(node)
	// sendMsg(userId, []byte("欢迎进入聊天系统"))
	// 7. 加入用户信息到缓存中
	SetUserOnlineInfo("online_"+id, []byte(node.Addr), time.Duration(viper.GetInt("timeout.RedisOnlineTime"))*time.Hour)
}

// 数据发送逻辑(续从 node.DataQueue 通道接收数据，并将接收到的数据通过 WebSocket 发送出去。)
func sendProc(node *Node) {

	for data := range node.DataQueue {
		err := node.Conn.WriteMessage(websocket.TextMessage, data)
		if err != nil {
			fmt.Println(err)
			return
		}
	}
}

// 数据接收逻辑
func recvProc(node *Node) {
	for {
		_, data, err := node.Conn.ReadMessage()
		if err != nil {
			fmt.Println(err)
			return
		}
		broadMsg(data)
		fmt.Println("[ws] <<<< ", string(data))
	}
}

var udpsendChan chan []byte = make(chan []byte, 1024)

// 广播数据
func broadMsg(data []byte) {
	// 使用通道操作符 <- 将数据 data 发送到 udpsendChan 通道中
	udpsendChan <- data

}

// 初始化
func init() {
	utils.InitConfig()
	go udpSendProc()
	go udpRecvProc()
}

// 完成udp数据发送协程

// 完成 udp数据发送协程
func udpSendProc() {
	// 获取本地的IPv4地址
	ifaces, err := net.Interfaces()
	if err != nil {
		fmt.Println(err)
		return
	}

	var localIP net.IP
	for _, iface := range ifaces {
		if iface.Name == "en0" && iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagLoopback == 0 {
			addrs, err := iface.Addrs()
			if err != nil {
				fmt.Println(err)
				return
			}

			for _, addr := range addrs {
				if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {
					localIP = ipnet.IP
					break
				}
			}
		}
	}

	if localIP == nil {
		fmt.Println("Failed to find local IP address")
		return
	}
	con, err := net.DialUDP("udp", nil, &net.UDPAddr{
		// 一定要是你的 局域网下分配的ip
		IP:   localIP,
		Port: viper.GetInt("port.udp"),
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	defer con.Close()

	for data := range udpsendChan {
		_, err := con.Write(data)
		if err != nil {
			fmt.Println(err)
			return
		}
	}
}

// 完成 udp数据接收协程
func udpRecvProc() {
	con, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4zero,
		Port: viper.GetInt("port.udp"),
	})
	if err != nil {
		fmt.Println(err)
	}
	defer con.Close()
	for {
		var buf [512]byte
		n, err := con.Read(buf[0:])
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("udpRecvProc  data :", string(buf[0:n]))
		dispatch(buf[0:n])
	}
}

func dispatch(data []byte) {
	msg := Message{}
	err := json.Unmarshal(data, &msg)
	if err != nil {
		fmt.Println(err)
		return
	}
	switch msg.Type {
	case 1:
		sendMsg(msg.TargetId, data)
	}
}

// 加群
func JoinGroup(userId uint, keyword string) (int, string) {
	contact := Contact{}
	contact.OwnerId = userId
	contact.Type = 2
	community := Community{}
	utils.DB.Where("id= ? or name= ?", keyword, keyword).Find(&community)
	if community.ID == 0 {
		return -1, "没有找到群"
	}
	utils.DB.Where("owner_id = ? and target_id = ? and type=2", userId, community.ID).Find(&contact)
	if !contact.CreatedAt.IsZero() {
		return -1, "不能重复加群"
	} else {
		contact.TargetId = community.ID
		utils.DB.Create(&contact)
		return 0, "加群成功"
	}

}

// 发送消息
func sendMsg(userId int64, msg []byte) {
	rwLocaker.RLock()
	node, ok := clientMap[userId]
	rwLocaker.RUnlock()

	jsonMsg := Message{}
	json.Unmarshal(msg, &jsonMsg)
	ctx := context.Background()
	targetIdStr := strconv.Itoa(int(userId))
	userIdStr := strconv.Itoa(int(jsonMsg.UserId))
	jsonMsg.CreateTime = uint64(time.Now().Unix())
	r, err := utils.Red.Get(ctx, "online_"+userIdStr).Result()
	if err != nil {
		fmt.Println(err)
	}
	if r != "" {
		if ok {
			fmt.Println("sendMsg >>> userID: ", userId, "  msg:", string(msg))
			node.DataQueue <- msg
		}
	}

	var key string
	if userId > jsonMsg.UserId {
		key = "msg_" + userIdStr + "_" + targetIdStr
	} else {
		key = "msg_" + targetIdStr + "_" + userIdStr
	}
	fmt.Println("redis key: ", key)
	// 获取有序集合中的最新消
	res, err := utils.Red.ZRevRange(ctx, key, 0, 1).Result()
	if err != nil {
		fmt.Println(err)
	}
	score := float64(cap(res) + 1)
	ress, e := utils.Red.ZAdd(ctx, key, &redis.Z{Score: score, Member: msg}).Result()
	if e != nil {
		fmt.Println(e)
	}
	fmt.Println(ress)

}

// 获取缓存里面的消息
func RedisMsg(userIdA int64, userIdB int64, start int64, end int64, isRev bool) []string {
	// rwLocker.RLock()
	//node, ok := clientMap[userIdA]
	// rwLocker.RUnlock()
	//jsonMsg := Message{}
	//json.Unmarshal(msg, &jsonMsg)
	ctx := context.Background()
	userIdStr := strconv.Itoa(int(userIdA))
	targetIdStr := strconv.Itoa(int(userIdB))
	var key string
	if userIdA > userIdB {
		key = "msg_" + targetIdStr + "_" + userIdStr
	} else {
		key = "msg_" + userIdStr + "_" + targetIdStr
	}
	//key = "msg_" + userIdStr + "_" + targetIdStr
	//rels, err := utils.Red.ZRevRange(ctx, key, 0, 10).Result()  //根据score倒叙

	var rels []string
	var err error
	if isRev {
		// 正序
		rels, err = utils.Red.ZRange(ctx, key, start, end).Result()
	} else {
		// 倒序
		rels, err = utils.Red.ZRevRange(ctx, key, start, end).Result()
	}
	if err != nil {
		fmt.Println(err) //没有找到
	}
	// 发送推送消息
	/**
	// 后台通过websoket 推送消息
	for _, val := range rels {
		fmt.Println("sendMsg >>> userID: ", userIdA, "  msg:", val)
		node.DataQueue <- []byte(val)
	}**/
	return rels
}

// 需要重写此方法才能完整的msg转byte[]
func (msg Message) MarshalBinary() ([]byte, error) {
	return json.Marshal(msg)
}
